module Visualizer::VisualisationTreeADTBuilder

import lang::java::jdt::Java;
import lang::java::jdt::JDT;
import lang::java::jdt::JavaADT;
import IO;
import Set;
import List;
import Relation;
import Time;

import Metrics::ModuleSize;
import Metrics::UnitSize;
import Metrics::CC;

import Visualizer::VisualisationTreeADT;
import Constants;


/*	Builds a VisualisationTree from method declarations defined as a BindingRel
	It does a visit of two different BindingRels, namely the classes and the
	methodDecls. This is done so the file location is included of both the
	classes and the method for later usage, like counting lines or showing in
	the visualisation.

	There is no support for interfaces as interfaces are not retrievable like the
	classes and methods.
	There is no support for inner classes, becuase they are to complex to handle
	for the short time of the assignment.
*/

str magicPackageName = "notsetyetmagicstring123notexsistingpackage"; // TODO: FIX THIS

// Method to generate a Visualisation Tree from a project.
public VisualisationTree generateVisTreeFromProject (loc project){
	projectTree = extractProject(project);
	
	BindingRel classes = projectTree@classes;
	BindingRel methoddecls = projectTree@methodDecls;
	AstNodeRel astNodes = projectTree@methodBodies;
	
	tree = buildVisTree(classes,methoddecls,astNodes);
	
	return tree;
}

// Builds a VisualisationTree.
public VisualisationTree buildVisTree(BindingRel classes,BindingRel methodDecls, AstNodeRel astNodes){
	int startAddClasses = getMilliTime();
	treeWithClasses =  addClassesToVisTree(classes);
	int endAddClasses = getMilliTime();
	int timeForClasses = (endAddClasses - startAddClasses)/1000;
	treeWithMethods = addMethodsToVisTree(treeWithClasses, methodDecls,astNodes );
	int timeForMethods = (getMilliTime() - endAddClasses)/1000;
	if ( timeForClasses<60) println("Time spent on putting classes in tree: <timeForClasses> seconds" ); else println("Time spent on putting classes in tree: <timeForClasses/60> minutes and <timeForClasses % 60> seconds");
	if ( timeForMethods<60) println("Time spent on putting methods in tree: <timeForMethods> seconds" ); else println("Time spent on putting methods in tree: <timeForMethods/60> minutes and <timeForMethods % 60> seconds");
	return treeWithMethods;
}

// Parses the BindingRel classes tree and creates a VisualisationTree for it.
private VisualisationTree addClassesToVisTree(BindingRel classes){

	VisualisationTree tree = package( magicPackageName, [],[]); // TODO, should not be needed i think, but no time to fix yet.
	
	for( loc fileLocation <- domain(classes)) {
	
		bool lastVisitWasClass = false; // keeps track if the last visited node in the tree was a class. Aimed for handling inner classes
		list [str] packageNames= [];
		list [str] classNames= [];
		
		visit ( classes[fileLocation] ){
	        case package(packageName): {
		        							if (lastVisitWasClass){// Check if last visit was class to clear the package list
		        									packageNames= [];
		        									classNames= [];
		        									lastVisitWasClass = false;
		        							}
		        							packageNames = packageNames + [packageName];
	        							}
	        case class(className): {
	        							if (!lastVisitWasClass){// Last visit was not a class, so this class is not an inner class.
	        								Class newClass = constructClass(className, fileLocation, [], []);
	        								if (size(packageNames)>0){
	        									tree = addClassToVisTree(addPackagesToVisTree(tree, packageNames ) , head(packageNames),delete(packageNames,0), newClass);
	        								}
	        								else {
	        									tree = addClassToVisTree(tree , "Class Without Package",[], newClass);
	        								}
	        							}
	        							lastVisitWasClass = true;
	        							classNames = classNames + [className];
	        						}
		};
	}

	return tree;
}

// Adds a class to the Visualisation Tree
private VisualisationTree addClassToVisTree(VisualisationTree tree, str currentPackage, list[str] remainingPackages, Class newClass ){
	return
	top-down-break visit(tree ){
		case package(/<currentPackage>/ , packages, classes):
			{
				if (isEmpty(remainingPackages)){
					insert package(currentPackage,packages,addClassToList(classes, newClass));
				}
				else {
					tmpPackages = [];
					tmpNextPackage = head(remainingPackages);
					tmpRemainingPackages = delete(remainingPackages,0);
					for (VisualisationTree package <- packages){
						if (package.name == tmpNextPackage)	package = addClassToVisTree(package,tmpNextPackage ,tmpRemainingPackages , newClass);
						tmpPackages =  tmpPackages + package;
					};
					insert package(currentPackage ,tmpPackages,classes);
				}
		 	}
	};
}

private VisualisationTree addPackagesToVisTree(VisualisationTree tree, list[str] packageNames ){
	str previousPackage = "";
	for ( str packageName <- packageNames){
		tree = rewriteTreeWithPackage(tree, packageName,previousPackage);
		previousPackage = packageName;
	};
	return tree;
}

// Need to put visit in a method to be able to create a new tree.
private VisualisationTree rewriteTreeWithPackage(VisualisationTree tree, str packageName, str previousPackage){
	return
	top-down-break visit ( tree ) {
		case package(/<magicPackageName>/ , packages, classes): // case to handle the default package. TODO: needs to be removed/sorted.
			{ 
		 		insert package(packageName,packages,classes);
		 	}
		case package(previousPackage , packages,classes) :
			{
				insert package(previousPackage,addPackageToList(packages,packageName),classes);
			}
		};
}

// Adds a package to a package list 
private list[VisualisationTree] addPackageToList(list[VisualisationTree] packages, str packageName){
	bool packageNameExists = false;
	for (VisualisationTree package <- packages){
		if (package.name == packageName){
			packageNameExists = true;
		}
	}
	if (packageNameExists) 
		return packages;
	else 
		return packages + [package(packageName , [],[])];
}

// Adds a package to a package list 
private list[Class] addClassToList(list[Class] classes, Class newClass){
	bool classNameExists = false;
	for (Class currentClass <- classes){
		if (currentClass.name == newClass.name){
			classNameExists = true;
		}
	}
	if (classNameExists) 
		return classes;
	else 
		return classes + [newClass];
}

// Calculates the lines of code of a class and returns a new Class instance
private Class constructClass(str className, loc fileLocation, list[Method] methods, list[Class] innerClasses){
	int linesOfCode = countLOCForClass(fileLocation);
	return class(className, fileLocation, linesOfCode , methods);
}

/****************************************************
*
* Following section of the code contains all methods
* needed to add methods to the classes in the 
* visualisation tree visualisation
*
*****************************************************/

// Adds methods to the visualisation tree
private VisualisationTree addMethodsToVisTree(VisualisationTree tree, BindingRel methodDecls, AstNodeRel astNodes){
	for( loc fileLocation <- domain(methodDecls)) {
	
		bool lastVisitWasMethod = false; // keeps track if the last visited node in the tree was a class. Aimed for handling inner classes
		list [str] packageNames= [];
		list [str] classNames= [];

		// Get the entity for processing for the file location
		Entity currentEntity = getEntityFromMethodDecls( methodDecls,  fileLocation);
		// Get the AST node for the method, so we can caluculate the cyclomatic complexity.
		AstNode astNode = getEntityFromMethodDecls( astNodes,  currentEntity);
		
		for (Id theID <- currentEntity.id){
			switch ( theID) {
		        case package(packageName):
		        {
					if (lastVisitWasMethod){// Check if last visit was class to clear the package list
						packageNames= [];
						classNames= [];
						lastVisitWasMethod = false;
					}
					packageNames = packageNames + [packageName];
		        }
		        case class(className): 
		        {
		        	classNames = classNames + [className];
		        }
		        case method(methodName,_,_):
		        {
					if (!isEmpty(classNames)){// Does not take into account interfaces, so ignore it if the classnames list is empty.
						Method newMethod = constructMethod( methodName,  fileLocation, astNode);
						tree = addMethodToClass(tree, head(packageNames), delete(packageNames,0), head(classNames), delete(classNames,0), newMethod);
						int endTime = getMilliTime();
					}
					lastVisitWasMethod = true;
				}
			}
		}
	}
	return tree;
}

// Calculates the Cyclomatic Complexity and lines of code of a method and returns
// it as a new Method instance
private Method constructMethod(str methodName, loc fileLocation,AstNode astNodes){
	int cc = methodCyclometricComplexity(astNodes);
	int linesOfCode = countLOCForSingleMethod(fileLocation);
	return method(methodName,fileLocation,linesOfCode,cc);
}

// Gets a single AstNode for a Set of AstNodes. Example usag: A for loop where a visit
// does not work because as it will also visit the declarations of the method parameters
private Entity getEntityFromMethodDecls(BindingRel methodDecls, loc fileLocation){
		set[Entity] currentSetEntity = methodDecls[fileLocation];
		// assuming the set is only one element. Not seen any sets with more then one, upto now.
		if (size(currentSetEntity) > 1) throw "Size of entity set is greather then 1. size: <size(currentSetEntity)>. Software assumes there is only 1 Entity in the set. <currentSetEntity>";
		return getOneFrom(currentSetEntity);
}

// Gets a single AstNode for a Set of AstNodes. Example usage: A for loop where a visit 
// does not work because as it will also visit the declarations of the method parameters
private AstNode getEntityFromMethodDecls(AstNodeRel astNodeRelation, Entity entity){
		set[AstNode] astNodeSetForMethod = astNodeRelation[entity];
		// assuming the set is only one element. Not seen any sets with more then one, upto now.
		//if (size(astNodeSetForMethod) > 1) throw "Size of ASTNode set is greather then 1. size: <size(astNodeSetForMethod)>. Software assumes there is only 1 Entity in the set. <astNodeSetForMethod>";
		return getOneFrom(astNodeSetForMethod);
}


// Adds a method to a class. Must work with currentPackage and remainingPackage list because the regexp cant handle head(packageNames)
private VisualisationTree addMethodToClass(VisualisationTree tree, str currentPackage, list[str] remainingPackages, str currentClass, list[str] remainingClasses, Method newMethod){
	return
	top-down-break visit(tree ){
		case package(/<currentPackage>/ , packages, classes):
			{
				if (isEmpty(remainingPackages)){ // if there are no packages left, we are at the package we need to insert the class
					tmpClasses = addMethodToClassList(classes, currentClass, newMethod);
					insert package(currentPackage,packages,tmpClasses);
				}
				else {
					tmpPackages = [];
					tmpNextPackage = head(remainingPackages);
					tmpRemainingPackages = delete(remainingPackages,0);
					for (VisualisationTree package <- packages){
						if (package.name == tmpNextPackage) {
							package = addMethodToClass(package, head(remainingPackages), delete(remainingPackages,0), currentClass, remainingClasses, newMethod);
						}
						tmpPackages =  tmpPackages + package;
					};
					insert package(currentPackage ,tmpPackages,classes);
				}
		 	}
	};
}

// Adds a method to a class list.
private list[Class] addMethodToClassList(list[Class] classes, str currentClass, Method newMethod){
	return
	for (Class tmpClass <- classes){
		if (tmpClass.name == currentClass){
			if (!(newMethod in tmpClass.methods)) {
					tmpClass.methods =  tmpClass.methods + [newMethod];
				}
		}
		append tmpClass;
	};
}